#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <cstring>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <algorithm>
#include <assert.h>
using namespace std;
const int N = 1024;
char cmd_line[N];
char* g_argv[64];    // 用于储存分割后的命令
// 写一个环境变量buffer
char g_myval[N];

// 定义几个重定向 宏
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define NONE_REDIR 0

// 默认为没有重定向
int redir_status = NONE_REDIR;

char* CheckRedir(char* start)
{
  assert(start);
  // end要指向被打开的文件
  char* end=start+strlen(start)-1;  // "ls -a -l\0", 此时end指向-l
  while(end >= start){
    if(*end=='>'){
      // 我们只考虑两种情况
      // echo aaa>log.txt
      // echo bbb>>log.txt
      if(*(end-1)=='>'){
        redir_status=APPEND_REDIR;
        *(end-1)='\0';
        end++;   
        break;
      }
      redir_status=OUTPUT_REDIR;
      *end='\0';
      end++;
      break;
    }
    else if(*end=='<'){
      // cat<log.txt  输入重定向
      redir_status=INPUT_REDIR;
      *(end)='\0';
      end++;
      break;
    }
    else end--;
  }
  // 通过break跳出，证明有重定向，返回打开的文件
  if(end >= start)  return end;
  // 没有重定向
  return NULL;
}
// shell运行原理： 通过让子进程执行命令，父进程等待&&解析命令
int main()
{
  // 命令行解释器一定是一个常住内存的解释器，不退出
  while(true)
  {
    // 1. 打印出提示信息
    cout<<"[root@localhost myshell]# ";
    fflush(stdout);   // 需要手动刷新缓冲区 
    // 2. 获取用户的键盘输入
    fgets(cmd_line, sizeof cmd_line, stdin);
    cmd_line[strlen(cmd_line)-1]='\0';
    // 2.1分析是否有重定向， "ls -a -l>log.txt"
    char *sep=CheckRedir(cmd_line);
    // 3. 命令字符串解析
    g_argv[0]=strtok(cmd_line, " ");
    int i=1;    // g_argv[N]的下标 
    if(strcmp(g_argv[0], "ls") == 0)
    {
      g_argv[i++]=(char*)"--color=auto";
    }
    else if(strcmp(g_argv[0], "ll")==0)
    {
      g_argv[0]=(char*)"ls";
      g_argv[i++]=(char*)"-l";
      g_argv[i++]=(char*)"--color=auto";
    }
    while(g_argv[i++]=strtok(NULL," "));
    if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL)
    {
      strcpy(g_myval, g_argv[1]);
      putenv(g_myval);
      continue;
    }
    // 4.内置命令，让父进程（shell)自己执行的命令，叫做内置命令或者内建命令
    if(strcmp(g_argv[0], "cd") == 0)
    {
      if(g_argv[1] != NULL) chdir(g_argv[1]);
    }
    
    // 5.fork()
    pid_t id = fork();
    if(id == 0)
    {
      // child
      if(sep != NULL){
        int fd = 0;
        switch(redir_status){
        case INPUT_REDIR:
          fd = open(sep, O_RDONLY);
          dup2(fd, 0);
          break;
        case OUTPUT_REDIR:
          // cout<<"执行了输出重定向"<<endl;
          fd=open(sep, O_WRONLY|O_TRUNC|O_CREAT, 0666);
          dup2(fd, 1);
          break;
        case APPEND_REDIR:
          fd = open(sep, O_WRONLY|O_APPEND|O_CREAT, 0666);
          dup2(fd, 1);
          break;
        default:
          cout<<"error"<<endl;
          break;
        }
       
      }
      // 当执行cd命令(cd ..)时，只会影响子进程的当前目录，不会影响父进程的路径，所以有第四步
      execvp(g_argv[0], g_argv);
      exit(66);
    }
    // father
    int status = 0;
    pid_t ret = waitpid(id, &status, 0);
    if(ret > 0)
      cout<<"退出码："<<WEXITSTATUS(status)<<endl;
  }
  return 0;
}
